import requests
import logging
import pathlib
from dateutil.parser import parse
from datetime import datetime
from datetime import timedelta
from dataclasses import dataclass
import pickle
import sys
import pandas as pd
from tqdm import tqdm
import pdb


def getGLogger(filename):
    logFormatter = logging.Formatter('%(asctime)s %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s')
    rootLogger = logging.getLogger()
    rootLogger.setLevel(logging.WARN)
    fh = logging.FileHandler('{}/{}.log'.format(pathlib.Path().absolute(), filename))
    fh.setFormatter(logFormatter)
    rootLogger.addHandler(fh)
    ch = logging.StreamHandler()
    ch.setFormatter(logFormatter)
    rootLogger.addHandler(ch)
    return rootLogger


logger = getGLogger("fmeter")


def loadTransactionRecordList(fpath):
    with open(fpath) as f:
        trl = [line.strip().split(",") for line in f]
        logger.info("Transaction record list length: {}".format(len(trl)))
        trl_result = [(x[0], datetime.fromtimestamp(float(x[1]) / 1e9),
                      timedelta(seconds=float(x[2]) / 1e9)) for x in trl]
        trl_df = pd.DataFrame(trl_result, columns=["id", "start_time", "elapsed"])
        return trl_df


@dataclass
class TransactionRecord(object):
    id: str
    start_time: datetime = None
    elapsed: timedelta = None
    endorsements_time: datetime = None
    takes: timedelta = None

    def calcTakes(self):
        self.takes = self.endorsements_time - self.start_time


def saveTA2File(ta, fpath):
    with open(fpath, "wb") as f:
        pickle.dump(ta, f, pickle.HIGHEST_PROTOCOL)


def loadTA(fpath):
    with open(fpath, "rb") as f:
        ta = pickle.load(f)
        return ta


class TransactionAnalyzer(object):

    def __init__(self, transaction_record_df=None, fm=None):
        if transaction_record_df is not None:
            self.transaction_record_df = transaction_record_df  # 改用dataframe，在最初columns=["id","start_time","elapsed"]，后来增加[“endorsements_time”，“takes”]
        if fm:
            self.__mergeDict(fm.trans_endorsements_time_dict)
            self.trans_block_distribution = pd.Series(fm.trans_distribution)  # 用于记录各块大小，也就是块内交易数
            self.size_distribution = pd.Series(fm.size_distribution)

    def save(self, fpath):
        """Summary
        Args:
            fpath (str): 用于存储的*.h5文件
        """
        store = pd.HDFStore(fpath)
        store.put("transaction_record_df", self.transaction_record_df)  # 记录所有交易的开始时刻、请求耗时、出块时刻、出块耗时等情况
        store.put("trans_block_distribution", self.trans_block_distribution)  # 记录各块交易数
        store.put("size_distribution", self.size_distribution)  # 记录各块大小（字节）
        store.close()

    def load(self, fpath):
        self.transaction_record_df = pd.read_hdf(fpath, 'transaction_record_df')
        self.trans_block_distribution = pd.read_hdf(fpath, 'trans_block_distribution')
        self.size_distribution = pd.read_hdf(fpath, 'size_distribution')

    def __mergeDict(self, tt_dict):
        """为交易记录补充交易对应的出块时刻，顺便记录出块延迟
        Args:
            tt_dict (TYPE): Description
        """
        for i, row in self.transaction_record_df.iterrows():
            try:
                self.transaction_record_df.at[i, 'endorsements_time'] = tt_dict[row["id"]][0]
                self.transaction_record_df.at[i, 'takes'] = tt_dict[row["id"]][0] - row["start_time"]
            except:
                pdb.set_trace()

    def getNumberOfRecord(self):
        return len(self.transaction_record_df.index)

    def getDuration(self):
        min_t = self.transaction_record_df["start_time"].min()
        max_t = self.transaction_record_df["start_time"].max()
        return (max_t - min_t).total_seconds()

    def getFinishedDuration(self):
        min_t = self.transaction_record_df["start_time"].min()
        max_t = self.transaction_record_df["endorsements_time"].max()
        return (max_t - min_t).total_seconds()

    def averageTPS(self):
        tps = float(self.getNumberOfRecord()) / self.getDuration()
        return tps

    def averageCTPS(self):
        ctps = float(self.getNumberOfRecord()) / self.getFinishedDuration()
        return ctps

    def avgHowLongItTakes(self):
        """平均出块秒数
        Returns:
            TYPE: Description
        """
        return self.transaction_record_df["takes"].mean().total_seconds()


class FMeter(object):

    """用于获取链上基本信息以及区块信息。
       TODO：通过成员变量配置区块链的HTTP接口地址
    """
    def __init__(self, url):
        self.url = url
        self.trans_endorsements_time_dict = {}      # 以交易ID为key的字典结构，存储从链上取到的出块时刻
        self.trans_distribution = []
        self.size_distribution = []

    @staticmethod
    def __getJSON(url):
        r = requests.get(url)
        data = r.json()
        size = len(r.content)
        # logger.info(data)
        return (data, size)

    def getChainInfo(self):
        data, _ = self.__getJSON("{}/chaininfo".format(self.url))
        return data

    def getOneBlock(self, height_index):
        data, size = self.__getJSON("{}/block/{}".format(self.url, height_index))
        return (data, size)

    def run(self):
        self.height = int(self.getChainInfo()["result"]["height"])
        for i in tqdm(range(2, self.height + 1), ncols=50):
            block, size = self.getOneBlock(i)
            block_result = block["result"]
            ts_list = [parse(x["tmLocal"]).replace(tzinfo=None) for x in block_result["endorsements"]]
            ts = max(ts_list)
            raw_transactions = block_result["transactions"]
            self.trans_distribution.append(len(raw_transactions))
            self.size_distribution.append(size)
            for trans in raw_transactions:
                tid = trans["id"]
                self.trans_endorsements_time_dict[tid] = (ts, i)


if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("usage: {} hostaddr:port saveto.h5".format(sys.argv[0]))
        sys.exit()
    fm = FMeter("http://{}".format(sys.argv[1]))
    fm.run()
    trl = loadTransactionRecordList("../go_request_result.txt")
    ta = TransactionAnalyzer(trl, fm)
    print("Number of blocks: {}".format(len(ta.trans_block_distribution)))
    print("Average number of transaction in each block: {}".format(ta.trans_block_distribution.mean()))
    print("Average bytes of each block: {}".format(ta.size_distribution.mean()))
    print("Number of transactions: {}".format(ta.getNumberOfRecord()))
    print("Average TPS: {}".format(ta.averageTPS()))
    print("Duration /seconds: {}".format(ta.getDuration()))
    print("Average finish time /seconds: {}".format(ta.avgHowLongItTakes()))
    print("Average CTPS: {}".format(ta.averageCTPS()))
    print("Finish Duration /seconds: {}".format(ta.getFinishedDuration()))
    ta.save(sys.argv[2])
